/*
 Copyright (c) 2013- MeherTJ G.
 All rights reserved. Everybody can use these code freely.
 */

#pragma once

#include <vector>
#include <memory.h>
#include <ios>

/**

  @brief
  A memory binary buffer.

  class Buffer {
  set
  size
  bytes
  value
  seek...
  tell...
  put
  Get
  write
  read
  }

  class BufferWriter {
  seekp(); tellp(); write();
  }

  class BufferReader {
  seekGetter(); tellGetter(); read();
  }

  */
namespace Zen
{

    class BufferWriter
    {
      public:
        BufferWriter() = delete;
        BufferWriter(BufferWriter const&) = delete;
        BufferWriter(BufferWriter&&) = delete;
        void operator=(BufferWriter const&) = delete;

        explicit BufferWriter(std::vector<uint8_t>& target)
          : m_outbuf(target)
        {
        }

        virtual ~BufferWriter() = default;

        void SetPutter(long pos)
        {
            long size = (long)m_outbuf.size();

            if (pos < 0)
                m_wp = size + pos;
            else
                m_wp = pos;

            if (m_wp > size)
                m_wp = size;
            else if (m_wp < 0)
                m_wp = 0;
        }
        void MovePutter(long off)
        {
            m_wp += off;
            if (m_wp >= m_outbuf.size())
                m_wp = (long)m_outbuf.size();
            else if (m_wp < 0)
                m_wp = 0;
        }

        long GetPutter() const
        {
            return m_wp;
        }

        template <typename _Type>
        void Write(_Type const& value)
        {
            return Write((void const*)&value, sizeof(_Type));
        }
        void Write(void const* buf, size_t size)
        {
            auto value = (const uint8_t*)buf;
            if (m_wp + (long)size > (long)m_outbuf.size())
            {
                m_outbuf.resize(m_wp + size, 0);
            }
            std::copy_n(value, size, m_outbuf.begin() + m_wp);
            m_wp += (long)size;
        }
        void WriteString(std::string const& s, bool tailed = false)
        {
            Write(s.c_str(), s.size() + (tailed ? 1 : 0));
        }

        void PutChar(uint8_t c)
        {
            if (m_wp < (long)m_outbuf.size())
            {
                m_outbuf.at(m_wp) = c;
            }
            else
            {
                m_outbuf.push_back(c);
            }
        }

      private:
        std::vector<uint8_t>& m_outbuf;
        long m_wp = 0;
    };

    class BufferReader
    {
      public:
        BufferReader() = delete;
        BufferReader(BufferReader const&) = delete;
        BufferReader(BufferReader&&) = delete;
        void operator=(BufferReader const&) = delete;

        BufferReader(std::vector<uint8_t> const& target)
          : m_inbuf(target)
        {
        }

        virtual ~BufferReader() = default;

        long GetGetter() const
        {
            return m_rp;
        }
        void SetGetter(long off)
        {
            long size = (long)m_inbuf.size();

            if (off < 0)
                m_rp = size + off;
            else
                m_rp = off;

            if (m_rp > size)
                m_rp = size;
            else if (m_rp < 0)
                m_rp = 0;
        }
        void MoveGetter(long off)
        {
            m_rp += off;
            if (m_rp >= (long)m_inbuf.size())
                m_rp = (long)m_inbuf.size();
            else if (m_rp < 0)
                m_rp = 0;
        }

        template <typename _Type>
        bool Read(_Type& value)
        {
            return Read((void*)&value, sizeof(value));
        }

        bool Read(void* buf, size_t size)
        {
            if (m_rp < 0 || m_rp + size > m_inbuf.size()) return false;

            auto start = m_inbuf.begin() + m_rp;
            auto end = start + (long)size;
            std::copy(start, end, (uint8_t*)buf);
            m_rp += (long)size;
            return true;
        }

        std::string ReadString() // string end with 0. if no 0 found return "".
        {
            if (m_rp < 0 || size_t(m_rp) > m_inbuf.size()) return "";

            auto start = m_inbuf.begin() + m_rp;
            auto end = std::find(start, m_inbuf.end(), 0);
            if (end == m_inbuf.end()) return "";
            auto size = std::distance(start, end);
            m_rp += (long)size;
            return std::string(start, end);
        }

        std::string ReadString(size_t size)
        {
            if (m_rp < 0 || size_t(m_rp) + size > m_inbuf.size()) return "";

            auto start = m_inbuf.begin() + m_rp;
            auto end = start + (long)size;
            m_rp += (long)size;
            return std::string(start, end);
        }

        int GetChar()
        {
            if (m_rp < 0 || size_t(m_rp) >= m_inbuf.size()) return -1;
            return (int)m_inbuf[m_rp++];
        }

      private:
        std::vector<uint8_t> const& m_inbuf;
        long m_rp = 0;
    };

    class Buffer : public BufferReader, public BufferWriter
    {
      public:
        Buffer(size_t size = 0)
          : BufferReader(mData)
          , BufferWriter(mData)
        {
            mData.resize(size);
        }

        Buffer(std::vector<uint8_t> buffer)
          : BufferReader(mData)
          , BufferWriter(mData)
        {
            Set(buffer);
        }

        void Set(size_t size)
        {
            mData.resize(size);
        }

        void Set(std::vector<uint8_t> buffer)
        {
            mData = std::move(buffer);
        }

        void Resize(size_t size)
        {
            mData.resize(size);
        }

        void Clear()
        {
            mData.clear();
        }
        size_t Size() const
        {
            return mData.size();
        }
        std::vector<uint8_t> const& Data() const
        {
            return mData;
        }

      protected:
        std::vector<uint8_t> mData;
    };
} // namespace Zen
